﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

namespace PacCraft
{
    class Monster
    {
        private Sprite3D model;
        private char[][] map;

        private BoundingBox Box = new BoundingBox();
        private List<BoundingBox> wallBox = new List<BoundingBox>();
        private Graph graph;

        private Ray leftRay = new Ray();
        private Ray rightRay = new Ray();
        private float angleRay = MathHelper.Pi / 6;

        private VertexPositionColor[] vertex = new VertexPositionColor[4];

        private Vector2 velocity = new Vector2(0,0);
        private Vector2 goal = new Vector2(0,0);
        private float speed = 0.03f;
        private int turn = 0;
        private int countGoal = 0;
        private Vector2 prevPos = new Vector2();
        private BoundingBox stuckWall;
        private bool seePlayer;
        private int distanceHear = 5;

        private Random random = new Random();
        private float reduceDim = 0.3f;
        private float redimRot = 5f;

        public Monster(Model _player, Map _map, Graph _graph)
        {
            model = new Sprite3D(_player);
            model.scale = new Vector3(0.009f, 0.014f, 0.009f);
            map = _map.map;
            graph = _graph;
            initPosition();
            updateBox();
            updateRay();
        }

        private void initPosition()
        {
            int x = 0;
            int z = 0;
            for (; map[x][z] != '0'; x = random.Next(0, map.Length - 1), z = random.Next(0, map[0].Length - 1));
            model.position = new Vector3(x + 0.4f, 1f, z + 0.4f);
        }

        private void updateRay()
        {
            vertex[0] = new VertexPositionColor(new Vector3(model.position.X, 2f, model.position.Z), Color.Red);
            vertex[1] = new VertexPositionColor(model.position + new Vector3((float)Math.Sin((double)model.rotationY + angleRay), 1f, (float)Math.Cos((double)model.rotationY + angleRay)), Color.Red);
            vertex[2] = new VertexPositionColor(model.position + new Vector3((float)Math.Sin((double)model.rotationY - angleRay), 1f, (float)Math.Cos((double)model.rotationY - angleRay)), Color.Red);
            leftRay.Position = vertex[0].Position;
            leftRay.Direction = new Vector3((float)Math.Sin((double)model.rotationY + angleRay), 1f, (float)Math.Cos((double)model.rotationY + angleRay));
            rightRay.Position = vertex[0].Position;
            rightRay.Direction = new Vector3((float)Math.Sin((double)model.rotationY - angleRay), 1f, (float)Math.Cos((double)model.rotationY - angleRay));
        }

        private void updateBox()
        {
//            Box.Min = new Vector3(model.position.X - reduceDim + ((float)Math.Sin(model.rotationY) / redimRot), 2f, model.position.Z - reduceDim + ((float)Math.Cos(model.rotationY) / redimRot));
//            Box.Max = new Vector3(model.position.X + reduceDim + ((float)Math.Sin(model.rotationY) / redimRot), 3f, model.position.Z + reduceDim + ((float)Math.Cos(model.rotationY) / redimRot));
            Box.Min = new Vector3(model.position.X - reduceDim, 2f, model.position.Z - reduceDim);
            Box.Max = new Vector3(model.position.X + reduceDim, 3f, model.position.Z + reduceDim);
        }

        private void changeGoal()
        {
            ++countGoal;
            if (seePlayer == false)
                if (countGoal > 500 || (Math.Abs(model.position.X - goal.X) < 1f && Math.Abs(model.position.Z - goal.Y) < 1f)
                || goal.X == 0)
                {
                    int x = 0;
                    int y = 0;
                    int Xmin = (int)model.position.X - 5;
                    int Xmax = (int)model.position.X + 5;
                    int Ymin = (int)model.position.Z - 5;
                    int Ymax = (int)model.position.Z + 5;

                    if (Xmin < 0)
                        Xmin = 0;
                    if (Ymin < 0)
                        Ymin = 0;
                    if (Xmax >= map.Length)
                        Xmax = map.Length - 1;
                    if (Ymax >= map[0].Length)
                        Ymax = map[0].Length - 1;
                    for (; map[x][y] != '0'; x = random.Next(Xmin, Xmax), y = random.Next(Ymin, Ymax)) ;
                    goal = new Vector2(x + 0.5f, y + 0.5f);
                    countGoal = 0;
                }
        }

        private bool doesIntersect(BoundingBox nextBox)
        {
            List<BoundingBox> walls = new List<BoundingBox>();
            int currentX = (int)model.position.X;
            int currentY = (int)model.position.Z;
            Vector3 min;
            Vector3 max;

            for (int i = -1; i <= 1; ++i)
                for (int j = -1; j <= 1; ++j)
                {
                    if (currentX + i >= 0 && currentY + j >= 0 && map[currentX + i][currentY + j] == '1')
                    {
                        min = new Vector3(currentX + i, 0f, currentY + j);
                        max = new Vector3(currentX + i + 1, 5f, currentY + j + 1);
                        walls.Add(new BoundingBox(min, max));
                    }
                }
            wallBox = walls;
            foreach (BoundingBox wall in walls)
            {
                if (nextBox.Intersects(wall))
                    return true;
            }
            return false;
        }


        private bool moveTo(float nextX, float nextY)
        {
            BoundingBox nextBox = new BoundingBox();
//            float rotat = -(float)Math.Atan2(velocity.Y, velocity.X) + (MathHelper.Pi / 2);
//            nextBox.Min = new Vector3(nextX - reduceDim + ((float)Math.Sin(rotat) / redimRot), 2f, nextY - reduceDim + ((float)Math.Cos(rotat) / redimRot));
//            nextBox.Max = new Vector3(nextX + reduceDim + ((float)Math.Sin(rotat) / redimRot), 3f, nextY + reduceDim + ((float)Math.Cos(rotat) / redimRot));
            nextBox.Min = new Vector3(nextX - reduceDim, 2f, nextY - reduceDim);
            nextBox.Max = new Vector3(nextX + reduceDim, 3f, nextY + reduceDim);

            if (!doesIntersect(nextBox))
            {
                model.position = new Vector3(nextX, 1f, nextY);
                model.rotationY = -(float)Math.Atan2(velocity.Y, velocity.X) + (MathHelper.Pi / 2);
                prevPos = new Vector2(nextX, nextY);
                updateBox();
                updateRay();
                return true;
            }
            updateBox();
            updateRay();
            return false;
        }

        private void leftColision(float dist)
        {
            Vector2 direction = new Vector2((float)Math.Sin((double)model.rotationY - angleRay), (float)Math.Cos((double)model.rotationY - angleRay));
            for (int x = 0; x < 100; x++)
            {
                direction.Normalize();
                direction += velocity;
            }
            velocity = direction;
//            verifRotation();
            velocity.Normalize();
            if (dist < 1)
                velocity *= speed * dist;
            else
                velocity *= speed;
            turn = 0;
        }

        private void rightColision(float dist)
        {
            Vector2 direction = new Vector2((float)Math.Sin((double)model.rotationY + angleRay), (float)Math.Cos((double)model.rotationY + angleRay));
            for (int x = 0; x < 100; x++)
            {
                direction.Normalize();
                direction += velocity;
            }
            velocity = direction;
//            verifRotation();
            velocity.Normalize();
            if (dist < 1)
                velocity *= speed * dist;
            else
                velocity *= speed;
            turn = 0;
        }

        private void turnAround(float left, float right)
        {
            float rotat = model.rotationY;
            if (turn == 1 || (left != 0 && left < right)) // true
            {
                turn = 1;
                rotat += 0.05f;
            }
            else
            {
                turn = 2;
                rotat -= 0.05f;
            }
            if (left == 0 && right == 0)
                turn = 0;
            velocity.X = (float)Math.Sin(rotat);
            velocity.Y = (float)Math.Cos(rotat);
//            verifRotation();
            velocity.Normalize();
            velocity *= 0.000001f;
        }


        private bool rayIntersect()
        {
            float leftDist = 0;
            float rightDist = 0;
            foreach (BoundingBox wall in wallBox)
            {
                if (leftRay.Intersects(wall) != null && (leftDist > leftRay.Intersects(wall) || leftDist == 0) && leftRay.Intersects(wall) < 1)
                    leftDist = (float)leftRay.Intersects(wall);
                if (rightRay.Intersects(wall) != null && (rightDist > rightRay.Intersects(wall) || rightDist == 0) && rightRay.Intersects(wall) < 1)
                    rightDist = (float)rightRay.Intersects(wall);
            }
            if (leftDist != 0 || rightDist != 0)
            {
                if (turn != 0)
                    turnAround(leftDist, rightDist);
                if (leftDist == 0)
                    rightColision(rightDist);
                else if (rightDist == 0)
                    leftColision(leftDist);
                else if (rightDist != 0 && leftDist != 0 && leftDist < 0.6f && rightDist < 0.6f)//Math.Abs(leftDist - rightDist) < 0.5f)
                    turnAround(leftDist, rightDist);
                else if (rightDist < leftDist)
                    rightColision(rightDist);
                else if (leftDist < rightDist)
                    leftColision(leftDist);

                if (moveTo(model.position.X + velocity.X, model.position.Z + velocity.Y))
                    return true;
                else if (!moveTo(model.position.X + velocity.X, model.position.Z))
                    moveTo(model.position.X, model.position.Z + velocity.Y);
                return true;
            }
            else
                return false;
        }

        private void verifRotation()
        {
//            Console.WriteLine(Math.Abs(-(float)Math.Atan2(velocity.Y, velocity.X) + (MathHelper.Pi / 2) + model.rotationY) +" > "+ MathHelper.Pi);
            if (Math.Abs(-(float)Math.Atan2(velocity.Y, velocity.X) + (MathHelper.Pi / 2) - model.rotationY) > (MathHelper.Pi / 10))
            {
//                Console.WriteLine("Help");
//                velocity.X = (float)Math.Sin(model.rotationY + (MathHelper.Pi / 10));
//                velocity.Y = (float)Math.Cos(model.rotationY + (MathHelper.Pi / 10));
            }

        }

        private void seek()
        {
            if (!rayIntersect())
            {
                Vector2 direction;
                direction = new Vector2((goal.X - model.position.X), (goal.Y - model.position.Z));
                for (int x = 0; x < 70; ++x)
                {
                    direction.Normalize();
                    direction += new Vector2(velocity.X, velocity.Y);
                }
                velocity = direction;
                verifRotation();
                velocity.Normalize();
                velocity *= speed;
                if (moveTo(model.position.X + velocity.X, model.position.Z + velocity.Y))
                    return;
                else if (!moveTo(model.position.X + velocity.X, model.position.Z))
                    moveTo(model.position.X, model.position.Z + velocity.Y);
            }
        }

        private bool checkPlayerVisible(Vector2 pPosition)
        {
            Ray toPlayer = new Ray();
            toPlayer.Position = new Vector3(model.position.X, 2f, model.position.Z);
            toPlayer.Direction = new Vector3((pPosition.X - model.position.X) * 0.6f, 2f, (pPosition.Y - model.position.Z) * 0.6f);
            foreach (BoundingBox wall in wallBox)
                if (toPlayer.Intersects(wall) != null)
                    return false;
            return true;
        }

        private bool checkPlayerClose(Vector2 pPosition)
        {
            Vector2 Direction;
            Direction = new Vector2((pPosition.X - model.position.X) * 0.6f, (pPosition.Y - model.position.Z) * 0.6f);
            if (Math.Abs(Direction.X) < distanceHear && Math.Abs(Direction.Y) < distanceHear)
                return true;
            return false;
        }

        public bool move(Vector2 pPosition, BoundingBox pBox)
        {
            if (Box.Intersects(pBox))
                return true;
            else if (checkPlayerVisible(pPosition))
            {
                goal = pPosition;
                seePlayer = true;
            }
            else if (checkPlayerClose(pPosition))
            {
                List<Node> nodes = new List<Node>();
                nodes = graph.getPath(new Vector2((int)model.position.X, (int)model.position.Z), pPosition);
                if (nodes.Count() > 1)
                    goal = nodes[1].getPosition() + new Vector2(0.5f);
                else
                    goal = nodes[0].getPosition() + new Vector2(0.5f);
                seePlayer = true;
            }
            else
            {
                changeGoal();
                seePlayer = false;
            }
            seek();
            return false;
        }

        public Vector2 getPosition()
        {
            return new Vector2(model.position.X, model.position.Z);
        }

        // Draw
        private float getAlpha(Player player, int i, int j)
        {
            Vector2 playerPosition = player.getPosition();

            float distance = (float)Math.Sqrt(Math.Pow(playerPosition.X - i, 2) + Math.Pow(playerPosition.Y - j, 2));
            return 1 - 0.1f * distance;
        }

        public void DrawBox(BasicEffect basicEffect)
        {
            foreach(BoundingBox wall in wallBox)
                BoxRender.Render(wall, basicEffect.GraphicsDevice, basicEffect.View, basicEffect.Projection, Color.White);

            BoxRender.Render(Box, basicEffect.GraphicsDevice, basicEffect.View, basicEffect.Projection, Color.White);

            basicEffect.CurrentTechnique.Passes[0].Apply();

            short[] lineStripIndices = new short[3] { 1, 0, 2};
            basicEffect.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
                PrimitiveType.LineStrip,
                vertex,
                0,   // vertex buffer offset to add to each element of the index buffer
                3,   // number of vertices to draw
                lineStripIndices,
                0,   // first index element to read
                2    // number of primitives to draw
            );
        }

        public void Draw(Player player)
        {
            model.alpha = getAlpha(player, (int)model.position.X, (int)model.position.Z);

            model.Draw(player.getCamera());
        }

    }
}
